package bman.filecopy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import bman.tools.JString;
import bman.tools.net.ByteStation;
import bman.tools.net.Radio;
import bman.tools.net.ByteReceiver;
import bman.tools.net.Receiver;
import bman.tools.net.SocketSession;

/**
 * Use this class in order to navigate through the files system
 * @author MrJacky
 *
 */
public class FileSys implements Receiver, ByteReceiver
{
	Logger log = Logger.getLogger(this.getClass().getName());
	
	Radio radio;
	String startingDir = ".";
	
	public static void main(String[] args) throws Exception
	{
		FileSys fs = new FileSys(".",777);
	}
	
	public FileSys(String startingDirectory, int listenPort) throws Exception
	{
		radio = new Radio(this,listenPort);
		startingDir = startingDirectory;
	}
	
	ByteStation bt = new ByteStation(this);
	
	/**
	 * Send file protocol:
	 * 
	 * 1. 4bytes (int) - send length of the filename 
	 * 2. send the full filename
	 * 3. 16bytes (long) - send length of the file
	 * 4. send the contents of the file 
	 * 
	 * @param file
	 * @param dest ip:port
	 * @throws IOException
	 */
	
	public void sendFile(File file, String dest)
			throws IOException
	{
		log.info("Sending file: "+file +". Does it exist: "+file.exists());
		
		log.info("\n" +
				"FileName length: "+file.getName().length()+"\n"+
				"FileName length: "+file.getName()+"\n"+
				"FileSize: "+file.length()+"\n" +
			    "Address: "+dest);

		SocketSession session = bt.createSession(dest);
		session.send(file.getName().length());
		session.send(file.getName());
		session.send(file.length());
		session.send(file);
	}

	Map sessionMap = new HashMap();
	
	public Session getSession(String id)
	{
		Session s = (Session)sessionMap.get(id);
		if(s == null)
		{
			s = new Session(id);
			sessionMap.put(id,s);
		}
		return s;
	}
	
	public void receive(Socket s, byte[] message)
	{
		String cmd = new String(message);
		log.info("Command received: "+cmd);

		try
		{
			Session session = getSession(s.toString());
			
			if(cmd.startsWith("ls"))
			{
				radio.reply(s, session.ls().getBytes());
			}
			else if(cmd.startsWith("cd"))
			{
				String dir = cmd.substring(3).trim();
				boolean b = session.cd(dir);
				String reply = "";
				if(b == true)
				{
					reply = session.pwd();
				}
				else
				{
					reply = dir+" does not exists or is not a directory.";
				}
				radio.reply(s,reply.getBytes());
			}
			else if(cmd.startsWith("pwd"))
			{
				radio.reply(s,session.pwd().getBytes());
			}
			else if(cmd.startsWith("cp"))
			{
				/*
				 * The format of the cp command is:
				 * cp <port> <filename>
				 */
				
				String port = JString.getMatch(".+\\s(\\d+)\\s.+", cmd);
				String fileNames = JString.getMatch(".+\\s\\d+\\s(.+)", cmd);
				String address = s.getInetAddress().getHostAddress()+":"+port;
				
				log.info("cp will connect to "+address+" and copy the following files");
				String[] files = splitFileNames(fileNames);
				for(int i=0;i<files.length;i++)
				{
					log.info("Pattern["+i+"] = "+files[i]);
					String fileName = session.pwd()+File.separatorChar+files[i];
					File pwd = new File(session.pwd());
					String[] globbedFiles = pwd.list(new Filter(files[i]));

					radio.reply(s,("Found "+globbedFiles.length+" files to copy").getBytes());

					for(int j=0;j<globbedFiles.length;j++)
					{
						log.info("Pattern["+i+"].Globbed["+j+"] = "+globbedFiles[j]);
						sendFile(new File(session.pwd()+File.separatorChar+globbedFiles[j]), address);
					}
				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**
	 * Splits splits filenames by spaces.
	 * <p>
	 * For example a string like this: <br>
	 * file1.txt file2.txt filewithnoextension "file wiht space.html"
	 * <p>
	 * ...will be split like this: <br>
	 * <ul>
	 * <li>file1.txt</li>
	 * <li>file2.txt</li>
	 * <li>filewithnoextension</li>
	 * <li>file wiht space.html</li>
	 * </ul>
	 * @param cmd
	 * @return
	 */
	public static String[] splitFileNames(String cmd)
	{
		List<String> stringList = new ArrayList();
		CharArrayWriter caw = new CharArrayWriter();
		char[] charArray = cmd.toCharArray();
		boolean quoteDetected = false;
		for(int i = 0; i < charArray.length; i++)
		{
			char c = charArray[i];
			
			boolean isQuote  = (c == '"'); 
			boolean lastChar = (i == (charArray.length -1));
			boolean isSpace  = c==' ';
			
			if(isQuote)
			{
				quoteDetected = !quoteDetected;
			}
			else if(isSpace && quoteDetected == false)
			{
				stringList.add(caw.toString());	
				caw.reset();
			}
			else
			{
				caw.append(c);
			}
			
			if(lastChar)
			{
				String last = caw.toString();
				if(last.trim().length() > 0)
				{
					stringList.add(caw.toString());
				}
			}
		}
		
		String[] stringArray = new String[stringList.size()];
		return stringList.toArray(stringArray);
	}
	
	private byte[] toByteArray(Object o) throws Exception
	{
		ByteArrayOutputStream bao = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(bao);
		oos.writeObject(o);
		oos.close();
		return bao.toByteArray();

	}

	public void receive(SocketSession session)
	{
		// TODO Auto-generated method stub
		
	}
}
